// Package service
// Created by GoLand
// @User: lenora
// @Date: 2024/8/5
// @Time: 15:49

package service

import (
	intrv1 "camp/microbook/api/proto/gen/interactive"
	"camp/microbook/internal/domain"
	"camp/microbook/internal/repository"
	"context"
	"errors"
	"github.com/ecodeclub/ekit/queue"
	"github.com/ecodeclub/ekit/slice"
	"math"
	"time"
)

type RankingService interface {
	TopN(ctx context.Context) ([]domain.Article, error)
	GetTop(ctx context.Context) ([]domain.Article, error)
}

func NewArticleRankingService(svc ArticleService,
	intrSvc intrv1.InteractiveServiceClient,
	rankingRepository repository.RankingRepository) RankingService {
	return &articleRankingService{
		svc:       svc,
		intrSvc:   intrSvc,
		repo:      rankingRepository,
		batchSize: 100,
		biz:       "article",
		count:     50,
		scoreFunc: func(cnt int64, ut time.Time) float64 {
			duration := time.Since(ut).Seconds()
			return float64(cnt-1) / math.Pow(duration+2, 1.5)
		},
	}
}

type articleRankingService struct {
	svc       ArticleService
	intrSvc   intrv1.InteractiveServiceClient
	repo      repository.RankingRepository
	batchSize int
	biz       string
	count     int
	scoreFunc func(cnt int64, ut time.Time) float64
}

func (svc *articleRankingService) GetTop(ctx context.Context) ([]domain.Article, error) {
	return svc.repo.GetTop(ctx)
}

func (svc *articleRankingService) TopN(ctx context.Context) ([]domain.Article, error) {
	list, err := svc.topN(ctx)
	if err != nil {
		return nil, err
	}
	return list, svc.repo.Sync(ctx, list)
}

// topN get top
// step:
//
//	1.批量获取n条文章数据
//	2.批量获取这些文章的点赞数
//	3.根据点赞数使用一个基于小顶堆实现的优先级队列生成榜单
//
// @return []domain.Article
// @return error
func (svc *articleRankingService) topN(ctx context.Context) ([]domain.Article, error) {
	offset := 0
	start := time.Now()

	type Score struct {
		item  domain.Article
		score float64
	}

	tops := queue.NewPriorityQueue[Score](svc.count, func(src Score, dst Score) int {
		if src.score > dst.score {
			return 1
		} else if src.score < dst.score {
			return -1
		} else {
			return 0
		}
	})

	for {
		list, err := svc.svc.ListByOffset(ctx, start, offset, svc.batchSize)
		if err != nil {
			return nil, err
		}
		ids := slice.Map[domain.Article, uint64](list, func(idx int, src domain.Article) uint64 {
			return src.Id
		})
		maps, err := svc.intrSvc.GetByIds(ctx, &intrv1.GetByIdsReq{
			Biz: svc.biz,
			Ids: ids,
		})
		if err != nil {
			return nil, err
		}
		mapping := maps.GetRes()
		for _, a := range list {
			intr := mapping[a.Id]
			ele := Score{
				item:  a,
				score: svc.scoreFunc(intr.LikeCnt, a.Utime),
			}
			//将元素插入队列
			err = tops.Enqueue(ele)
			if errors.Is(err, queue.ErrOutOfCapacity) {
				//队列已满,需要剔除元素
				//获取队列score值最小元素与当前元素进行比较
				minEle, _ := tops.Peek()
				if minEle.score < ele.score {
					_, _ = tops.Dequeue()
					_ = tops.Enqueue(ele)
				}
			}
		}
		if len(list) < svc.batchSize {
			break
		}
		offset = offset + svc.batchSize
	}

	res := make([]domain.Article, tops.Len())
	for i := tops.Len() - 1; i >= 0; i-- {
		ele, _ := tops.Dequeue()
		res[i] = ele.item
	}
	return res, nil
}
